package org.miniframework.mvc.processor.impl;

import lombok.extern.slf4j.Slf4j;
import org.miniframework.core.BeanContainer;
import org.miniframework.mvc.RequestProcessorChain;
import org.miniframework.mvc.annotation.RequestMapping;
import org.miniframework.mvc.annotation.RequestParam;
import org.miniframework.mvc.annotation.ResponseBody;
import org.miniframework.mvc.processor.RequestProcessor;
import org.miniframework.mvc.render.JsonResultRender;
import org.miniframework.mvc.render.ResourceNotFoundResultRender;
import org.miniframework.mvc.render.ResultRender;
import org.miniframework.mvc.render.ViewResultRender;
import org.miniframework.mvc.type.ControllerMethod;
import org.miniframework.mvc.type.RequestPathInfo;
import org.miniframework.util.ConverterUtil;
import org.miniframework.util.ValidationUtil;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import static jdk.nashorn.api.scripting.ScriptUtils.convert;

/**
 * Controller请求处理器
 */
@Slf4j
public class ControllerRequestProcessor implements RequestProcessor {
    // IOC 容器
    private BeanContainer beanContainer;
    // 请求和controller方法的集合
    private Map<RequestPathInfo, ControllerMethod> pathInfoControllerMethodMap = new ConcurrentHashMap<>();

    /**
     * 依靠容器的能力建立起请求路径，请求方法与controller方法实例的映射
     */
    public ControllerRequestProcessor(){
        this.beanContainer = BeanContainer.getInstance();
        Set<Class<?>> requestMappingSet = this.beanContainer.getClassesByAnnotation(RequestMapping.class);
        initPathInfoControllerMethodMap(requestMappingSet);
    }

    private void initPathInfoControllerMethodMap(Set<Class<?>> requestMappingSet) {
        if (ValidationUtil.isEmpty(requestMappingSet)){return;}
        // 1.遍历所有被@RequestMapping标记的类，获取类上面该注解属性值作为一级路径
        for (Class<?> requestMappingClass : requestMappingSet){
            RequestMapping methodRequest = requestMappingClass.getAnnotation(RequestMapping.class);
            String basePath = methodRequest.value();
            if (!basePath.startsWith("/")){
                basePath = "/" + basePath;
            }
            // 2.遍历类里所有被@RequestMapping标记的方法，获取方法上该注解属性值，作为二级路径
            Method[] methods = requestMappingClass.getDeclaredMethods();
            if (ValidationUtil.isEmpty(methods)){
                continue;
            }
            for (Method method : methods){
                if (method.isAnnotationPresent(RequestMapping.class)){
                    RequestMapping methodMapping = method.getAnnotation(RequestMapping.class);
                    String methodPath = methodMapping.value();
                    if (!methodPath.startsWith("/")){
                        methodPath = "/" + methodPath;
                    }
                    String url = basePath + methodPath;
                    // 3.解析方法里被@RequestMapping标记的参数
                    // 获取注解的属性值，作为参数名
                    // 获取被标记的参数的数据类型，建立参数名和参数类型的映射
                    HashMap<String, Class<?>> methodParams = new HashMap<>();
                    Parameter[] parameters = method.getParameters();
                    if (!ValidationUtil.isEmpty(parameters)){
                        for (Parameter parameter : parameters){
                            RequestParam param = parameter.getAnnotation(RequestParam.class);
                            // 目前暂定为controller方法所有参数都需要@RequestParam注解
                            if (param == null){
                                throw new RuntimeException("The parameter must have @RequestParam");
                            }
                            methodParams.put(param.value(),parameter.getType());
                        }
                    }
                    // 4.将获取到的信息封装成RequestPathInfo实例和ControllerMethod实例，放置到映射表里
                    String httpMethod = String.valueOf(methodRequest.method());
                    RequestPathInfo requestPathInfo = new RequestPathInfo(httpMethod, url);
                    if (this.pathInfoControllerMethodMap.containsKey(requestPathInfo)){
                        log.warn("duplicate url:{} registration，current class {} method{} will override the former one",requestPathInfo.getHttpPath(),requestMappingClass.getName(),method.getName());
                    }
                    ControllerMethod controllerMethod = new ControllerMethod(requestMappingClass, method, methodParams);
                    this.pathInfoControllerMethodMap.put(requestPathInfo,controllerMethod);
                }
            }
        }

    }

    @Override
    public boolean process(RequestProcessorChain chain) throws Exception {
        // 1.解析httpSevletRequest的请求方法/请求路径，获取对应的ControllerMethod实例
        String method = chain.getRequestMethod();
        String path = chain.getRequestPath();
        ControllerMethod controllerMethod = this.pathInfoControllerMethodMap.get(new RequestPathInfo(method, path));
        if (controllerMethod == null){
            chain.setResultRender(new ResourceNotFoundResultRender(method,path));
            return false;
        }
        // 2.解析请求参数，并传递给获取到的ControlelrMethod实例去执行
        Object result = invokeControllerMethod(controllerMethod,chain.getRequest());
        // 3.根据解析的结果，选择对应的render进行渲染
        setResultRender(result,controllerMethod,chain);
        return true;
    }

    /**
     * 处理完成之后 给chain中的 resultRender进行设值,使得chain可以调用指定resultRender,进行结果的处理响应
     * @param result           返回值
     * @param controllerMethod controllerMethod
     * @param chain            RequestProcessorChain
     */
    private void setResultRender(Object result,ControllerMethod controllerMethod, RequestProcessorChain chain) {
        if (result == null){
            return;
        }
        ResultRender resultRender;
        if (controllerMethod.getInvokeMethod().isAnnotationPresent(ResponseBody.class)){
            resultRender = new JsonResultRender(result);
        }else{
            resultRender = new ViewResultRender(result);
        }
        chain.setResultRender(resultRender);
    }

    private Object invokeControllerMethod(ControllerMethod controllerMethod, HttpServletRequest request) {
        // 1.从请求里获取GET或POST的参数名及对应的值
        // 解析请求参数 目前参数只支持1-1 不支持 1-string[]
        Map<String,String> requestParamMap = new HashMap<>();
        Map<String, String[]> parameterMap = request.getParameterMap();
        for (Map.Entry<String,String[]> parameter : parameterMap.entrySet()){
            if (!ValidationUtil.isEmpty(parameter.getValue())){
                // 只支持一个参数对应一个值的形式
                requestParamMap.put(parameter.getKey(),parameter.getValue()[0]);
            }
        }
        // 2.根据解析的请求参数与controllerMethod中参数名-参数类型 去实例化出方法对应的参数
        List<Object> methodParams = new ArrayList<>();
        Map<String, Class<?>> methodParameters = controllerMethod.getMethodParameters();
        for (String paramName : methodParameters.keySet()){
            Class<?> paramType = methodParameters.get(paramName);
            String requestParamValue = requestParamMap.get(paramName);
            Object value;
            // 只支持String 以及基础类型char,int,short,byte,double,long,float,boolean,及它们的包装类型
            if (requestParamValue == null){
                // 将请求里的参数值转成适配于参数类型的空值
                value = ConverterUtil.primitiveNull(paramType);
            }else{
                value = ConverterUtil.convert(paramType,requestParamValue);
            }
            methodParams.add(value);
        }
        // 3.反射执行方法并返回结果
        Object containerBean = beanContainer.getBean(controllerMethod.getControllerClass());
        Method invokeMethod = controllerMethod.getInvokeMethod();
        invokeMethod.setAccessible(true);
        Object result;
        try {
            if (methodParams.size() == 0){
                result = invokeMethod.invoke(containerBean);
            }else{
                result = invokeMethod.invoke(containerBean,methodParams.toArray());
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            // 如果调用异常，需要通过 e.getTargetException()
            // 去获取执行方法抛出的异常
            throw new RuntimeException(e.getTargetException());
        }
        return result;
    }
}
